﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Xml;
using SeasideResearch.LibCurlNet;
using System.Threading;
using System.Security.Authentication;

namespace SemestralniPraceY35VAN
{
    class Downloader
    {
        public const string METHOD_GET = "GET";
        public const string METHOD_POST = "POST";
        public const int TIMEOUT = 15000;

        /// <summary>
        /// 
        /// </summary>
        private static string header;
        private static string data;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="buf"></param>
        /// <param name="size"></param>
        /// <param name="nmemb"></param>
        /// <param name="extraData"></param>
        /// <returns></returns>
        public static Int32 OnHeaderData(Byte[] buf, Int32 size, Int32 nmemb, Object extraData)
        {
            header += System.Text.Encoding.UTF8.GetString(buf);

            return size * nmemb;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="buf"></param>
        /// <param name="size"></param>
        /// <param name="nmemb"></param>
        /// <param name="extraData"></param>
        /// <exception cref="TimeoutException"></exception>
        /// <returns></returns>
        public static Int32 OnWriteData(Byte[] buf, Int32 size, Int32 nmemb, object extraData)
        {
            data += System.Text.Encoding.UTF8.GetString(buf);

            return size * nmemb;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="url"></param>
        /// <param name="method"></param>
        /// <param name="parameters"></param>
        /// <param name="username"></param>
        /// <param name="password"></param>
        /// <returns></returns>
        public static XmlDocument ProcessRequest(string url, string method, string parameters, string username, string password)
        {
            header = "";
            data = "";

            Curl.GlobalInit((int)CURLinitFlag.CURL_GLOBAL_ALL);

            Easy easy = new Easy();

            if (!String.IsNullOrEmpty(method) && method.ToUpper() == METHOD_GET && !String.IsNullOrEmpty(parameters))
            {
                url += "?" + parameters;
            }

            easy.SetOpt(CURLoption.CURLOPT_URL, url);

            Easy.WriteFunction writeFunction = new Easy.WriteFunction(OnWriteData);
            Easy.HeaderFunction headerFunction = new Easy.HeaderFunction(OnHeaderData);

            easy.SetOpt(CURLoption.CURLOPT_HEADERFUNCTION, headerFunction);
            easy.SetOpt(CURLoption.CURLOPT_WRITEFUNCTION, writeFunction);
            easy.SetOpt(CURLoption.CURLOPT_USERAGENT, Foursquare.USER_AGENT);

            if (!String.IsNullOrEmpty(method) && method.ToUpper() == METHOD_POST)
            {
                easy.SetOpt(CURLoption.CURLOPT_POST, 1);
                easy.SetOpt(CURLoption.CURLOPT_POSTFIELDS, parameters);
            }

            if (!String.IsNullOrEmpty(username) && !String.IsNullOrEmpty(password))
            {
                easy.SetOpt(CURLoption.CURLOPT_HTTPAUTH, CURLhttpAuth.CURLAUTH_BASIC);
                easy.SetOpt(CURLoption.CURLOPT_USERPWD, username + ":" + password);
            }

            // request execution

            Thread threadToKill = null;

            Action wrappedAction = () =>
            {
                threadToKill = Thread.CurrentThread;
                easy.Perform();
            };

            IAsyncResult result = wrappedAction.BeginInvoke(null, null);

            if (result.AsyncWaitHandle.WaitOne(TIMEOUT, false))
            {
                wrappedAction.EndInvoke(result);
            }
            else
            {
                threadToKill.Abort();
                throw new TimeoutException();
            }

            easy.Cleanup();

            Curl.GlobalCleanup();

            XmlDocument xml = stringToXml(data);

            XmlNodeList unauthorized = xml.GetElementsByTagName("unauthorized");

            if (unauthorized.Count > 0)
            {
                throw new AuthenticationException(unauthorized[0].InnerText);
            }

            XmlNodeList error = xml.GetElementsByTagName("error");

            if (error.Count > 0)
            {
                throw new Exception(error[0].InnerText);
            }

            XmlNodeList ratelimited = xml.GetElementsByTagName("ratelimited");

            if (ratelimited.Count > 0)
            {
                throw new Exception(ratelimited[0].InnerText);
            }

            return xml;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="url"></param>
        /// <param name="method"></param>
        /// <param name="parameters"></param>
        /// <returns></returns>
        public static XmlDocument ProcessRequest(string url, string method, string parameters)
        {
            return ProcessRequest(url, method, parameters, null, null);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        public static XmlDocument ProcessRequest(string url)
        {
            return ProcessRequest(url, null, null);
        }

        /// <summary>
        /// Downloads data from URL and returns them in byte array.
        /// </summary>
        /// <param name="url">URL</param>
        /// <returns>byte array with downloaded data</returns>
        public static byte[] DownloadData(string url)
        {
            byte[] downloadedData = new byte[0];

            try
            {
                // get a data stream from the url
                WebRequest req = WebRequest.Create(url);
                WebResponse response = req.GetResponse();

                using (Stream stream = response.GetResponseStream())
                {
                    // download in chuncks
                    byte[] buffer = new byte[1024];

                    // get Total Size
                    int dataLength = (int)response.ContentLength;

                    // download to memory
                    using (MemoryStream memStream = new MemoryStream())
                    {
                        while (true)
                        {
                            // try to read the data
                            int bytesRead = stream.Read(buffer, 0, buffer.Length);

                            if (bytesRead == 0)
                            {
                                break;
                            }
                            else
                            {
                                // write the downloaded data
                                memStream.Write(buffer, 0, bytesRead);
                            }
                        }

                        // convert the downloaded stream to a byte array
                        downloadedData = memStream.ToArray();
                    }
                }
            }
            catch (Exception)
            {
                // may not be connected to the internet or the URL might not exist
                throw new IOException("There was an error accessing the URL.");
            }

            return downloadedData;
        }

        /// <summary>
        /// Downloads image from URL.
        /// </summary>
        /// <param name="url">URL</param>
        /// <returns>downloaded image</returns>
        public static Image DownloadImage(string url)
        {
            Image image = null;

            byte[] imageData = DownloadData(url);

            // create image from downloaded data
            using (MemoryStream stream = new MemoryStream(imageData))
            {
                image = Image.FromStream(stream);
            }

            return image;
        }

        /// <summary>
        /// Covert string to XML object.
        /// </summary>
        /// <param name="xml">string with XML</param>
        /// <returns>XML object</returns>
        public static XmlDocument stringToXml(string xml)
        {
            XmlDocument xmlDoc = new XmlDocument();
            xmlDoc.Load(new StringReader(xml));
            return xmlDoc;
        }
    }
}